package com.study.web.controller;

import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.extra.mail.MailException;
import com.study.app.service.IAppUserService;
import com.study.common.constant.Constants;
import com.study.common.core.KeyValue;
import com.study.common.core.controller.AppBaseController;
import com.study.common.core.domain.R;
import com.study.common.core.domain.model.RegistrationInformation;
import com.study.common.enums.SmsTemplateDataEnum;
import com.study.common.exception.ServiceException;
import com.study.common.model.ChuangLanSmsResult;
import com.study.common.utils.AesGcmUtils;
import com.study.common.utils.ChuangLanSmsUtils;
import com.study.common.utils.MessageUtils;
import com.study.common.utils.RedisKeyUtils;
import com.study.common.utils.email.MailUtils;
import com.study.common.utils.redis.RedisUtils;
import com.study.app.service.IAppLoginService;
import com.study.sms.client.SmsClientFactory;
import com.study.sms.client.SmsCommonResult;
import com.study.sms.client.dto.SmsSendRespDTO;
import com.study.sms.properties.SmsTemplateProperties;
import com.study.sms.util.TemplateDataUtils;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.text.MessageFormat;
import java.time.Duration;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 获取验证码
 *
 * @author MT
 */
@Slf4j
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping
public class SmsCodeController extends AppBaseController {


    private final IAppLoginService iAppLoginService;
    private final IAppUserService iAppUserService;
    private final SmsClientFactory smsClientFactory;
    private final SmsTemplateProperties smsTemplateProperties;
    private static final Pattern PATTERN = Pattern.compile("(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\\d{8}");

    private boolean checkPhone(String phone) {
        Matcher matcher = PATTERN.matcher(phone);
        return !matcher.matches();
    }

    /**
     * 账号注册获取验证码 - 邮箱 | 短信
     *
     * @param registrationInformation 短信参数信息
     * @return 结果
     */
    @SaIgnore
    @PostMapping("/smsRegisterCode")
    public R<Void> smsRegisterCode(@RequestBody RegistrationInformation registrationInformation) {
        String key;
        String code = RandomUtil.randomNumbers(6);

        log.info("发送验证码：{}", code);

        // 获取异常，使得提示文本符合项目原型
        try{
            if (registrationInformation.getType().equals(1)) {
                key = RedisKeyUtils.getAppSmsRegisterCode(registrationInformation.getPhone());
                return send(registrationInformation.getPhone(), key, code,
                        smsTemplateProperties.getRegister(), TemplateDataUtils.getData(code), "短信注册");
            } else if (registrationInformation.getType().equals(2)) {
                key = RedisKeyUtils.getAppEmailRegisterCode(registrationInformation.getEmail());
                return sendEmail(registrationInformation.getEmail(), key, code,
                        "Hello! Thank you for using deepCare APP, your email registration verification code is: #code#\n valid for 10 minutes, please do not disclose the verification code. If not performed by yourself, please ignore.", "邮件注册");
            } else {
                return R.fail();
            }
        }catch (MailException e){
            throw new ServiceException(MessageUtils.message("user.email.not.valid"));
        }

    }


    /**
     * 短信验证码登录，加条件判断，若用户没有注册，则调用注册进行返回
     *
     * @param areaCode 区号
     * @param phone    手机号码
     * @return 结果
     */
    @SaIgnore
    @GetMapping("/smsLoginCode")
    public R<Void> smsLoginCode(@RequestParam @NotNull String areaCode, @RequestParam @NotNull String phone) {
        // 检测手机号码
        if (!iAppLoginService.checkPhone(areaCode, phone)) {
            //这里表示当前用户未注册，去给用户发送注册验证码
            String key = RedisKeyUtils.getAppSmsRegisterCode(phone);
            String code = RandomUtil.randomNumbers(6);

            return send(phone, key, code,
                    smsTemplateProperties.getRegister(), TemplateDataUtils.getData(code), "注册");
        }
        String key = RedisKeyUtils.getAppSmsLoginCode(phone);
        String code = RandomUtil.randomNumbers(6);

        return send(phone, key, code,
                smsTemplateProperties.getLogin(), TemplateDataUtils.getData(code), "登录");
    }

    /**
     * 忘记密码验证码
     *
     * @param areaCode 区号
     * @param phone    手机号码
     * @return 结果
     */
    @SaIgnore
    @GetMapping("/smsForgetCode")
    public R<Void> smsForgetCode(@Valid @NotNull String areaCode, @Valid @NotNull String phone) {
        // 检测手机号码
        if (!iAppLoginService.checkPhone(areaCode, phone)) {
            return R.fail(MessageUtils.message("code.phone.no.register"));
        }

        String key = RedisKeyUtils.getAppSmsForgetCode(phone);
        String code = RandomUtil.randomNumbers(6);

        return send(phone, key, code,
                smsTemplateProperties.getForget(), TemplateDataUtils.getData(code), "忘记密码");
    }


    /**
     * 绑定手机号验证码
     *
     * @param areaCode 区号
     * @param phone    手机号码
     * @return 结果
     */
    @SaIgnore
    @GetMapping("/smsBindCode")
    public R<Void> smsBindCode(@RequestParam @NotNull String areaCode, @RequestParam @NotNull String phone) {

        if (iAppLoginService.checkPhone(areaCode,phone)) {
            return R.fail(MessageUtils.message("code.phone.exchange.new.exists")) ;
        }
        String key = RedisKeyUtils.getAppSmsBindCode(phone);
        String code = RandomUtil.randomNumbers(6);

        return send(phone, key, code,
                smsTemplateProperties.getBind(), TemplateDataUtils.getData(code), "绑定手机号");
    }

    /**
     * 更换手机号验证码
     *
     * @return 结果
     */
    @SaCheckLogin
    @SneakyThrows
    @GetMapping("/smsExchangeCode")
    public R<Void> smsExchangeCode() {

        String phone = AesGcmUtils.decrypt(getUserPhone());

        String key = RedisKeyUtils.getAppSmsExchangeCode(phone);
        String code = RandomUtil.randomNumbers(6);

        return send(phone, key, code,
                smsTemplateProperties.getExchange(), TemplateDataUtils.getData(code), "更换手机号");
    }

    /**
     * 用户绑定新手机号验证码
     *
     * @param areaCode 区号
     * @param phone    手机号
     */
    @SaCheckLogin
    @GetMapping("/smsRebind")
    public R<Void> smsRebind(@RequestParam @NotNull String areaCode, @RequestParam @NotNull String phone) {
        if (iAppLoginService.checkPhone(areaCode,phone)) {
            return R.fail(MessageUtils.message("code.phone.exchange.new.exists")) ;
        }
        String key = RedisKeyUtils.getAppRebindCode(phone);
        String code = RandomUtil.randomNumbers(6);

        return send(phone, key, code,
                smsTemplateProperties.getRebind(), TemplateDataUtils.getData(code), "绑定新手机号");
    }

    /**
     * 账号注销验证码
     *
     * @param phone 手机号码
     * @return 结果
     */
    @SaCheckLogin
    @GetMapping("/smsDeleteCode")
    public R<Void> smsDeleteCode(@RequestParam @NotNull String areaCode, @RequestParam @NotNull String phone) {
        if (!iAppLoginService.checkPhone(areaCode,phone)) {
            return R.fail(MessageUtils.message("code.phone.no.register")) ;
        }
        String key = RedisKeyUtils.getAppSmsDeleteCode(phone);
        String code = RandomUtil.randomNumbers(6);

        return send(phone, key, code,
                smsTemplateProperties.getDelete(), TemplateDataUtils.getData(code), "账号注销");
    }

    /**
     * 邮箱注销验证码
     *
     * @param email 邮箱
     * @return 结果
     */
    @SaCheckLogin
    @GetMapping("/emailDeleteCode")
    public R<Void> emailDeleteCode(@RequestParam @NotNull String email) {
        if (!iAppLoginService.checkEmail(email)) {
            return R.fail(MessageUtils.message("code.email.no.register"));
        }
        String key = RedisKeyUtils.getAppEmailDeleteCode(email);
        String code = RandomUtil.randomNumbers(6);

        return sendEmail(email, key, code,
                MessageUtils.message("code.email.delete.content"), "账号注销");
    }

    /**
     * 更换绑定邮箱验证码 - 获取当前账号邮箱验证码
     *
     * @return 结果
     */
    @SaCheckLogin
    @SneakyThrows
    @GetMapping("/exchangeMailCode")
    public R<Void> exchangeMailCode() {
        String email = AesGcmUtils.decrypt(getUserEmail());

        String key = RedisKeyUtils.getAppSmsExchangeCurrentCode(email);
        String code = RandomUtil.randomNumbers(6);

        return sendEmail(email, key, code,
                MessageUtils.message("code.email.exchange.content"), "更换绑定邮箱验证码");
    }


    /**
     * 忘记密码验证码
     *
     * @param email 邮箱
     * @return 结果
     */
    @SaIgnore
    @GetMapping("/emailForgetCode")
    public R<Void> emailForgetCode(@RequestParam @NotNull String email) {
        // 检测手机号码
        if (!iAppLoginService.checkEmail(email)) {
            return R.fail(MessageUtils.message("code.email.no.register"));
        }

        String key = RedisKeyUtils.getAppEmailForgetCode(email);
        String code = RandomUtil.randomNumbers(6);

        return sendEmail(email, key, code,
                MessageUtils.message("code.email.forget.content"), "忘记密码");
    }

    /**
     * 更换绑定邮箱验证码 - 获取新邮箱验证码
     *
     * @return 结果
     */
    @SaCheckLogin
    @SneakyThrows
    @GetMapping("/exchangeNewMailCode")
    public R<Void> exchangeNewMailCode(@RequestParam @NotNull String email) {

        if (iAppLoginService.checkEmail(email)) {
            return R.fail(MessageUtils.message("code.email.exchange.new.exists")) ;
        }
        String key = RedisKeyUtils.getAppEmailExchangeNewCode(email);
        String code = RandomUtil.randomNumbers(6);

        return sendEmail(email, key, code,
                MessageUtils.message("code.email.exchange.new.content"), "更换绑定新邮箱验证码");
    }

    /**
     * 创蓝短信
     * @param areaCode 区号
     * @param phone 手机号码
     * @param key 缓存key
     * @param value 缓存值
     * @param smsTemplateData 短信模板数据
     * @param content 短信内容
     * @return 短信发送结果
     */
    private R<Void> send(String areaCode,
                         String phone,
                         String key,
                         String value,
                         SmsTemplateDataEnum smsTemplateData,
                         String content) {
        // 10分钟不可重复发送
        if (RedisUtils.hasKey(key)) {
            throw new ServiceException(Constants.CAPTCHA_EXPIRATION + "分钟不可重复发送短信");
        }

        // 发送短信
        ChuangLanSmsResult result = ChuangLanSmsUtils.send(areaCode, smsTemplateData, phone, value);
        if (!"0".equals(result.getCode())) {
            log.error("{} - 验证码短信发送异常 => phone: {} result:{}", content, phone, result);
            return R.fail(result.getMessage());
        }

        // 缓存
        log.info("{} - 验证码缓存 - key: {} value: {}", content, key, value);
        RedisUtils.setCacheObject(key, value, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));

        log.info("{} - 短信发送成功. phone:{}, code:{}, result:{}", content, phone, value, result);

        return R.ok();
    }

    /**
     * 阿里短信
     * @param phone 手机号码
     * @param key 缓存key
     * @param value 缓存值
     * @param templateId 模板ID
     * @param data 模板参数
     * @param content 短信内容
     * @return 短信发送结果
     */
    private R<Void> send(String phone,
                         String key,
                         String value,
                         String templateId,
                         List<KeyValue<String, Object>> data,
                         String content) {
        // 10分钟不可重复发送
//        if (RedisUtils.hasKey(key)) {
//            throw new ServiceException(Constants.CAPTCHA_EXPIRATION + "分钟不可重复发送短信");
//        }

        // 发送短信
        SmsCommonResult<SmsSendRespDTO> result = smsClientFactory.getSmsClient().sendSms(phone, templateId, data);

//        if (!result.isSuccess()) {
//            log.error("{} - ali验证码短信发送异常 => phone: {} result:{}", content, phone, result);
//            return R.fail(result.getMsg());
//        }

        // 缓存
        log.info("{} - 验证码缓存 - key: {} value: {}", content, key, value);
        RedisUtils.setCacheObject(key, value, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));

        log.info("{} - 短信发送成功. phone:{}, code:{}, result:{}", content, phone, value, result);

        return R.ok();
    }

    private R<Void> sendEmail(String email,
                              String key,
                              String value,
                              String message,
                              String content) {
        // 10分钟不可重复发送
        if (RedisUtils.hasKey(key)) {
            throw new ServiceException(MessageFormat.format(
                    MessageUtils.message("code.cannot.send.email"), Constants.CAPTCHA_EXPIRATION));
        }

        //发送邮箱
        message = message.replace("#code#", value);
        String registration = MailUtils.sendText(email, MessageUtils.message("code.email.title"), message);
        log.info("{} - 邮箱发送 => email: {} result:{} ID:{}", content, email, message, registration);

        // 缓存
        log.info("{} - 邮件验证码缓存 - key: {} value: {}", content, key, value);
        RedisUtils.setCacheObject(key, value, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));

        log.info("{} - 邮件发送成功. email:{},code:{}", content, email, value);

        return R.ok();
    }

}
